package com.atguigu.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.HasStockVo;
import com.atguigu.common.to.mq.OrderTo;
import com.atguigu.common.to.mq.StockLockTo;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.atguigu.gulimall.ware.entity.WareOrderTaskEntity;
import com.atguigu.gulimall.ware.exception.NoStockException;
import com.atguigu.gulimall.ware.feign.MemberFeignService;
import com.atguigu.gulimall.ware.feign.OrderFeignService;
import com.atguigu.gulimall.ware.feign.ProductFeignService;
import com.atguigu.gulimall.ware.service.WareOrderTaskDetailService;
import com.atguigu.gulimall.ware.service.WareOrderTaskService;
import com.atguigu.gulimall.ware.vo.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.ware.dao.WareSkuDao;
import com.atguigu.gulimall.ware.entity.WareSkuEntity;
import com.atguigu.gulimall.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;



@Slf4j
@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired
    private WareSkuDao wareSkuDao;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private WareOrderTaskService wareOrderTaskService;

    @Autowired
    private WareOrderTaskDetailService wareOrderTaskDetailService;

    @Autowired
    private MemberFeignService memberFeignService;

    @Autowired
    private OrderFeignService orderFeignService;

    /**
     * 有两种情况都需要解锁订单
     * （1）订单保存成功，但过期未支付系统自动取消、或用户手动取消，都要解锁库存
     * （2）下订单的整个大事务中有异常，导致订单回滚，库存应该解锁
     * 这里应该先拿到库存工作单id查库，
     * 如果工作单id都不存在，说明库存锁定失败，锁定库存事务已整体回滚，不用解锁
     * 如果工作单id在库存工作单表中存在，说明库存锁定成功
     * 此时是否需要解锁还需要进一步判断
     * 应该首先拿着orderSn去订单系统查，如果这个订单都不存在，说明生成订单的大事务已回滚，需要解锁库存
     * 如果订单存在，要去判断订单的状态，如果是已支付什么的，不用解锁。反而应该实际扣减库存？
     * 如果订单状态是已取消，应该解锁库存
     */
    @Override
    @Transactional //应该加个事务，要不然一个消息里有的库存解锁了，有的没解锁，后续很难处理
    public void unlockStock(StockLockTo stockLockTo) throws IOException {
        Long wareOrderTaskId = stockLockTo.getId();WareOrderTaskEntity byId = wareOrderTaskService.getById(wareOrderTaskId);
        if (byId == null){
            //说明库存锁定失败，无需解锁
        }else {
            //先查询订单服务当前订单的状态
            R byOrderSn = orderFeignService.getByOrderSn(byId.getOrderSn());
            if (byOrderSn.getCode() == 0){
                OrderVo data = byOrderSn.getData(new TypeReference<OrderVo>() {
                });
                if (data == null || data.getStatus() == 4){
                    List<WareOrderTaskDetailEntity> orderTaskDetails =
                            wareOrderTaskDetailService.getBaseMapper().selectBatchIds(stockLockTo.getDetailIds());
                    for (WareOrderTaskDetailEntity detail : orderTaskDetails) {
                        this.unlockStock1(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detail.getId());
                        //解锁完后WareOrderTaskDetailEntity表中的status应更新为2-已解锁
                        WareOrderTaskDetailEntity update = new WareOrderTaskDetailEntity();
                        update.setId(detail.getId());
                        update.setLockStatus(2);
                        wareOrderTaskDetailService.updateById(update);
                    }
                }
            }else{
                throw new RuntimeException("查询订单状态出错");
            }
        }
    }

    @Override
    @Transactional //这里也应该加事务，保证一个订单的库存一起解锁或没解锁，有的解锁了有的没解锁就比较乱了
    public void unlockStock(OrderTo order) {
        WareOrderTaskEntity wareOrderTask = wareOrderTaskService.
                getOne(new QueryWrapper<WareOrderTaskEntity>().eq("order_sn", order.getOrderSn()));
        List<WareOrderTaskDetailEntity> details = wareOrderTaskDetailService.getByOrderTaskId(wareOrderTask.getId());
        for (WareOrderTaskDetailEntity detail : details) {
            //TODO 这里确实会有并发时重复解锁的问题，两个服务同时进来，查状态都是1，都会update并执行减库存的操作
            if (detail.getLockStatus() == 1){ //1-锁定 2-解锁 3-已实际扣减库存
                this.unlockStock1(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detail.getId());
                WareOrderTaskDetailEntity update = new WareOrderTaskDetailEntity();
                update.setId(detail.getId());
                update.setLockStatus(2);
                wareOrderTaskDetailService.updateById(update);
            }
        }

    }

    private void unlockStock1(Long skuId, Long wareId, Integer skuNum,Long detailId) {
        this.getBaseMapper().unlockStock(skuId,wareId,skuNum);
        WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity();
        wareOrderTaskDetailEntity.setId(detailId);
        wareOrderTaskDetailEntity.setLockStatus(2);//2-解锁状态
        wareOrderTaskDetailService.updateById(wareOrderTaskDetailEntity);
    }

    private Integer tryLockStock(Long skuId, Long wareId, Integer count) {
        return this.baseMapper.tryLockStock(skuId,wareId,count);
    }


    @Override
    public PageUtils queryPage(Map<String, Object> params) {

        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

        String skuId = (String) params.get("skuId");
        if (StringUtils.isNotEmpty(skuId)){
            wrapper.eq("sku_id",skuId);
        }

        String wareId = (String) params.get("wareId");
        if (StringUtils.isNotEmpty(wareId)){
            wrapper.eq("ware_id",wareId);
        }

        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                wrapper
        );

        return new PageUtils(page);
    }



    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        List<WareSkuEntity> wareSkuEntities = wareSkuDao
                .selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if (wareSkuEntities == null || wareSkuEntities.size() == 0){
            WareSkuEntity wareSkuEntity = new WareSkuEntity();
            wareSkuEntity.setSkuId(skuId);
            wareSkuEntity.setWareId(wareId);
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setStockLocked(0);//默认0

            try {
                R info = productFeignService.info(skuId);
                Map<String,String> skuInfo = (Map<String, String>) info.get("skuInfo");
                String skuName = skuInfo.get("skuName");
                wareSkuEntity.setSkuName(skuName);
            } catch (Exception e) {
                log.info("远程获取sku_name失败，事务继续处理");
            }

            baseMapper.insert(wareSkuEntity);
        }else{
            wareSkuDao.addStock(skuId,wareId,skuNum);
        }
    }

    @Override
    public List<HasStockVo> hasStock(List<Long> skuIds) {
        return wareSkuDao.hasStock(skuIds);
    }

    @Override
    public FareVo fare(Long addrId) {
        R info = memberFeignService.info(addrId);
        if(info.getCode() == 0){
            MemberAddressVo memberReceiveAddress = info.getData(new TypeReference<MemberAddressVo>() {
            }, "memberReceiveAddress");
            //TODO 先取手机号最后一位作为运费
            String phone = memberReceiveAddress.getPhone();
            FareVo fareVo = new FareVo();
            fareVo.setFare(new BigDecimal(phone.substring(phone.length()-1)));
            fareVo.setAddress(memberReceiveAddress);
            return fareVo;
        }
        return null;
    }



    @Override
    @Transactional
    public boolean lockStock(WareSkuLockVo lockVo) {

        /**
         * 实际保存库存前先在库存工作单wms_ware_order_task中保存一个orderSn
         */
        WareOrderTaskEntity orderTaskEntity = new WareOrderTaskEntity();
        orderTaskEntity.setOrderSn(lockVo.getOrderSn());
        wareOrderTaskService.save(orderTaskEntity);



        /**
         * 本来是应该根据收获地址来优先锁就近的库存，这里就简单处理，直接依次锁就行
         * 也就是先查哪些仓库有库存，再依次尝试锁定
         * 这里不去弄一个skuId锁多个仓库的库存，加起来得锁定的总量的情况
         * 还是就简单处理，尝试在一个仓库全部锁定，如果不够就试下一个仓库
         */
        List<OrderItemVo> locks = lockVo.getLocks();

        ArrayList<Long> detailIds = new ArrayList<>();

        for (OrderItemVo lock : locks) {
            Long skuId = lock.getSkuId();
            SkuHasStockWare skuHasStockWare = findSkuHasStockWare(skuId);
            List<Long> wareIds = skuHasStockWare.wareIds;
            if (wareIds == null || wareIds.size() == 0){
                throw new NoStockException(skuId);
            }

            boolean skuLockFlag = false;
            for (Long wareId : wareIds) {
                //为每个skuId遍历有库存的仓库，尝试锁定库存
                //返回影响的行数，如果是1则说明锁定成功，为0说明锁定失败
                Integer count = tryLockStock(skuId,wareId,lock.getCount());
                if (count == 1){
                    skuLockFlag = true;
                    //只要锁定了一个仓库，剩下的就不用看了
                    //TODO 锁定了库存应该给发一个消息给MQ，一定时间后检查如果订单回滚、取消等，则自动解锁库存
                    //这消息应该发什么内容？
                    //订单号OrderSn,锁定库存的仓库wareId,锁定的库存数量count

                    WareOrderTaskDetailEntity wareOrderTaskDetailEntity =
                            new WareOrderTaskDetailEntity(null, skuId, null,
                                    lock.getCount(), orderTaskEntity.getId(), wareId, 1);
                    wareOrderTaskDetailService.save(wareOrderTaskDetailEntity);
                    detailIds.add(wareOrderTaskDetailEntity.getId());
                    break;
                }
            }
            //如果遍历完所有的wareId,skuLockFlag仍未false，说明没有都不满足，没有足够库存
            if (!skuLockFlag){
                //有一个skuId没有足够库存，就抛异常，剩下的skuId都不用查了
                //这里一抛异常，所有的库存锁定包括库存工作单等信息都回滚了
                throw new NoStockException(skuId);
            }
        }



        //到这里库存基本锁定成功了，发消息
        //这里如果发消息有异常，也会回滚
        StockLockTo stockLockTo = new StockLockTo();
        stockLockTo.setId(orderTaskEntity.getId());
        stockLockTo.setDetailIds(detailIds);
        //这里是一个订单最后一起发的一条消息
        //String exchange, String routingKey, Object message
        amqpTemplate.convertAndSend("stock-event-exchange","stock.lock",stockLockTo);

        //最后都没抛异常肯定就是都锁定成功了
        return true;
    }

    private SkuHasStockWare findSkuHasStockWare(Long skuId) {
        List<Long> wareIds = this.baseMapper.findSkuHasStockWare(skuId);
        SkuHasStockWare skuHasStockWare = new SkuHasStockWare();
        skuHasStockWare.setWareIds(wareIds);
        skuHasStockWare.setSkuId(skuId);
        return skuHasStockWare;
    }

    @Data
    public static class SkuHasStockWare{
        private Long skuId;
        private List<Long> wareIds;
    }
}